package generatorImplementations.misc;

import generator.Generator;
import generator.GeneratorType;
import generator.properties.AnimationPropertiesContainer;
import generatorImplementations.AnnotatedAlgorithm;

import java.awt.Color;
import java.awt.Font;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Locale;

import algoanim.animalscript.AnimalScript;
import algoanim.primitives.Rect;
import algoanim.primitives.SourceCode;
import algoanim.primitives.StringMatrix;
import algoanim.primitives.Text;
import algoanim.primitives.generators.Language;
import algoanim.properties.AnimationPropertiesKeys;
import algoanim.properties.MatrixProperties;
import algoanim.properties.RectProperties;
import algoanim.properties.SourceCodeProperties;
import algoanim.properties.TextProperties;
import algoanim.util.Coordinates;
import algoanim.util.Offset;
import algoanim.util.TicksTiming;
import algoanim.util.Timing;

public class ErweiterterEuklid extends AnnotatedAlgorithm implements Generator{

	static class SimpleFileWriter {
		public static void writeFile(File selSort, String data) {
			try {
				FileWriter fw = new FileWriter(selSort);
				BufferedWriter bw = new BufferedWriter(fw);
				bw.write(data);
				bw.close();
				fw.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	private String text = "Mit dem erweiterten euklidischen Algorithmus berechnet man ein " +
			"x und ein y, sodass die Gleichung ax+by = gcd(a, b) gel�st werden kann. Verwendet " +
			"wird er zum Beispiel bei der Schl�sselberechnung des RSA-Verschl�sselungs-" +
			"Verfahrens. Eingabedaten sind a und b.";

	
	private String code = "" 
		+ "xeuklid(int a, int b) @label(\"header\") @declare(\"int\", \"1000\") @declare(\"int\", \"27\")\n" 
		+ "begin @label(\"begin\""
		+ "int q = 0"
		+ "int r = 0"
		+ "int xtmp = 0"
		+ "int ytmp = 0"
		+ "int sign = 1"
		+ "int x0 = 1"
		+ "int x1 = 0"
		+ "int y0 = 0"
		+ "int y1 = 1";
	
	
	private String pseudoCodeBuchmann = ""
		+ "xeulid(int a, int b, int gcd, int x, int y) { 	@label(\"header\")\n"
		+ "begin 											@label(\"begin\")\n"
		+ "int q, r, xx, yy, sign 							@label(\"dec_vars\")"
		+		"@declare(\"int\", \"q\") @declare(\"int\", \"r\") @declare(\"int\", \"xx\") " +
				"@declare(\"int\", \"yy\") @declare(\"int\", \"sign\")\n"
		 + "int xs[2], ys[2] 								" +
		 		"@label(\"dec_array\") @declare(\"int\", \"xs0\") @declare(\"int\", \"xs1\") @declare(\"int\", \"ys0\") " +
				"@declare(\"int\", \"ys1\")\n"
		+ "xs[0] = 1 xs[1] = 0 							@label(\"init_xs\") @set(\"xs0\", \"1\") @set(\"xs1\", \"0\")\n"
		+ "  ys[0} = 0 ys[1] = 1 							@label(\"init_ys\")\n"
//		+		"@set(\"ys0\", \"0\")" +
//				"@set(\"ys1\", \"1\")\n"
		+ "  sign = 1 										@label(\"init_sign\")\n"
//		+		"@set(\"sign\", \"1\")\n"
		+ "  while (b != 0)									@label(\"while\")\n"
		+ "    r = a%b 										@label(\"calc_r\")\n" 
//		+		"@set(\"r\", \"a%b\")\n"
		+ "    q = a/b 										@label(\"calc_q\")\n" 
//		+		"@set(\"q\", \"a/b\")\n"
		+ "    a = b 										@label(\"calc_a\")\n" 
//		+ 		"@set(\"a\", \"b\")\n"
		+ "    b = r 										@label(\"calc_b\")\n" 
//		+	    "@set(\"b\", \"r\")\n"
		+ "    xx = xs[1] 									@label(\"calc_xx\")\n" 
//		+		"@set(\"xx\", \"xs1\")\n"
		+ "    yy = ys[1] 									@label(\"calc_yy\")\n" 
//		+		"@set(\"yy\", \"ys1\")\n"
		+ "    xs[1] = q*xs[1] + xs[0] 						@label(\"calc_xs1\")\n" 
//		+		"@set(\"xs1\", \"q*xs1 + xs0\")\n"
		+ "    ys[1] = q*ys[1] + ys[0] 						@label(\"calc_ys1\")\n" 
//		+ 		"@set(\"ys1\", \"q*ys1+ys0\")\n"
		+ "    xs[0] = xx 									@label(\"calc_xs0\")\n" 
//		+		"@set(\"xs0\", \"xx\")\n"
		+ "    ys[0] = yy 									@label(\"calc_ys0\")\n"
//		+		"@set(\"ys0\", \"yy\")\n"
		+ "    sign = -sign									@label(\"calc_sign\")\n" 
//		+		"@set(\"sign\", \"-sign\")\n"
		+ "  end while 										@label(\"end_while\")\n"
		+ "  x = sign*xs[0] 								@label(\"calc_x\")\n" 
//		+		"@set(\"x\", \"sign*xs0\")\n"
		+ "  y = -sign*ys[0] 								@label(\"calc_y\")\n" 
//		+		"@set(\"y\", \"-sign*ys\")\n"
		+ "  gcd = a 										@label(\"calc_gcd\")\n" 
//		+		"@set(\"gcd\", \"a\")\n"
		+ "end 												@label(\"end_function\")\n";

	/*
	 * Felder fuer Darstellung
	 */
	private final String HEADER_TITLE = "Euklidischer Algorithmus";
	private final String AUTHOR = "Tuba Goezel und Johannes Born";
	private final String PSEUDOCODE_TITLE = "Pseudocode aus \\\"Einfuehrung "
			+ "in die Kryptographie\\\" von Johannes Buchmann";
	private final String P_CODE_TITLE = "Pseudocode aus \"Einfuehrung "
		+ "in die Kryptographie\" von Johannes Buchmann";
	
	private Timing defaultTiming;
	private final String A_INIT = "a = ";
	private final String B_INIT = "b = ";

	private final String R = "r = a mod b = ";
	private final String Q = "q = a/b = ";
	private final String A = "a = b = ";
	private final String B = "b = r = ";

	private final String GCD = "gcd(a,b) = a = ";
	private final String X = "x = sign*xs[0] = ";
	private final String Y = "y = -sign*ys[0] = ";

	private Language lang;
	private Text header;
	private Rect headerBackground;
	private StringMatrix table;

	private List<String> rk = new ArrayList<String>();
	private List<String> qk = new ArrayList<String>();
	private List<String> xk = new ArrayList<String>();
	private List<String> yk = new ArrayList<String>();

	private Text textA;
	private Text textB;
	private Text textR;
	private Text textQ;
	private Text textX;
	private Text textY;
	private Text textGCD;
	
	public ErweiterterEuklid() {
		lang = new AnimalScript(HEADER_TITLE, AUTHOR, 640, 480);
		lang.setStepMode(true);
		header = generateHeader();
		headerBackground = generateHeaderBackground();
	}

	public void init() {
		super.init();
		Text sourceCodeHeader = generateCodeTitle(PSEUDOCODE_TITLE);
		SourceCodeProperties scProps = new SourceCodeProperties();
		scProps.set(AnimationPropertiesKeys.CONTEXTCOLOR_PROPERTY, Color.BLUE);
		scProps.set(AnimationPropertiesKeys.FONT_PROPERTY, new Font(
				"Monospaced", Font.PLAIN, 12));
		scProps.set(AnimationPropertiesKeys.HIGHLIGHTCOLOR_PROPERTY, Color.RED);
		scProps.set(AnimationPropertiesKeys.COLOR_PROPERTY, Color.BLACK);
		sourceCode = lang.newSourceCode(new Offset(0, -10, sourceCodeHeader,
				AnimalScript.DIRECTION_SW), "PseudoCode", null, scProps);
		parse();
	}

	private void generateText() {
		textA = lang.newText(
				new Offset(200, 0, sourceCode, AnimalScript.DIRECTION_NE), A_INIT,
				"A", null);
		textB = lang.newText(
				new Offset(0, 10, textA, AnimalScript.DIRECTION_SW), B_INIT,
				"B", null);
		textR = lang.newText(
				new Offset(0, 5, textB, AnimalScript.DIRECTION_SW), R, "A",
				null);
		textQ = lang.newText(
				new Offset(0, 5, textR, AnimalScript.DIRECTION_SW), Q, "A",
				null);
		textX = lang.newText(
				new Offset(0, 5, textQ, AnimalScript.DIRECTION_SW), X, "A",
				null);
		textY = lang.newText(
				new Offset(0, 5, textX, AnimalScript.DIRECTION_SW), Y, "A",
				null);
		textGCD = lang.newText(new Offset(0, 5, textY,
				AnimalScript.DIRECTION_SW), GCD, "A", null);
		textA.hide();
		textB.hide();
		textR.hide();
		textQ.hide();
		textX.hide();
		textY.hide();
		textGCD.hide();

	}

	/*
	 * Methoden fuer die Darstellung
	 */

	/**
	 * Generiert die Ueberschrift fuer die Darstellung des Erweiterten Euklid
	 * 
	 * @return Text header;
	 */
	private Text generateHeader() {
		TextProperties textProperties = new TextProperties();
		textProperties.set(AnimationPropertiesKeys.COLOR_PROPERTY, Color.BLACK);
		textProperties.set(AnimationPropertiesKeys.DEPTH_PROPERTY, 1);
		return lang.newText(new Coordinates(20, 30), HEADER_TITLE + " von "
				+ AUTHOR, "header", null, textProperties);
	}

	/**
	 * Generiert den Hintergrund fuer die Ueberschrift
	 * 
	 * @return headerBackground
	 */
	private Rect generateHeaderBackground() {
		RectProperties rectProperties = new RectProperties();
		rectProperties.set(AnimationPropertiesKeys.FILL_PROPERTY, Color.GRAY);
		rectProperties.set(AnimationPropertiesKeys.FILLED_PROPERTY, true);
		rectProperties.set(AnimationPropertiesKeys.DEPTH_PROPERTY, 2);
		return lang.newRect(new Offset(-5, -5, header,
				AnimalScript.DIRECTION_NW), new Offset(5, 5, header,
				AnimalScript.DIRECTION_SE), "HeaderBackground", null,
				rectProperties);
	}

	/**
	 * Erzeugt Titel f�r SourceCode
	 */
	private Text generateCodeTitle(String title) {
		TextProperties textProperties = new TextProperties();
		textProperties.set(AnimationPropertiesKeys.COLOR_PROPERTY, Color.BLACK);
		textProperties.set(AnimationPropertiesKeys.DEPTH_PROPERTY, 1);
		return lang.newText(new Offset(0, 20, headerBackground,
				AnimalScript.DIRECTION_SW), title, "sourcecodeheader", null,
				textProperties);
	}

	/**
	 * Erzeugt Tabelle fuer erweitertenEuklid Form: | q | r | x | y | a | b | x0
	 * | x1 | y0 | y1 |
	 */
	private void algorithmus() {
		String[][] matrix = toStringMatrix();
		String[][] stepMatrix = new String[matrix.length][matrix[0].length];
		MatrixProperties prob = new MatrixProperties();
		prob.set(AnimationPropertiesKeys.COLOR_PROPERTY, Color.BLACK);
		prob.set(AnimationPropertiesKeys.FILLED_PROPERTY, true);
		prob.set(AnimationPropertiesKeys.ELEMENTCOLOR_PROPERTY, Color.BLACK);
		prob.set(AnimationPropertiesKeys.FILL_PROPERTY, Color.WHITE);
		prob.set(AnimationPropertiesKeys.DEPTH_PROPERTY, 1);
		for (int i = 0; i < matrix.length; i++) {
			for (int j = 0; j < matrix[i].length; j++) {
				stepMatrix[i][j] = "";
			}
		}
		for (int i = 0; i < matrix.length; i++) {
			stepMatrix[i][0] = matrix[i][0];
		}
		for (int i = 0; i < matrix[0].length; i++) {
			stepMatrix[0][i] = matrix[0][i];
		}
		table = lang.newStringMatrix(new Offset(200, 200, sourceCode,
				AnimalScript.DIRECTION_NE), stepMatrix, "Matrix", null, prob);
		lang.nextStep();

		exec("header");
		table.put(1, 1, matrix[1][1], null, defaultTiming);
		table.put(1, 2, matrix[1][2], null, defaultTiming);
		table.highlightCell(1, 1, null, defaultTiming);
		textA.setText(A_INIT + matrix[1][1], null, null);
		textA.show();
		table.highlightCell(1, 2, null, defaultTiming);
		textB.setText(B_INIT + matrix[1][2], null, null);
		textB.show();
		lang.nextStep();

		table.unhighlightCell(1, 1, null, defaultTiming);
		table.unhighlightCell(1, 2, null, defaultTiming);
		exec("dec_vars");
		textQ.show();
		textR.show();
		lang.nextStep();

		exec("init_xs");
		exec("init_ys");
		table.put(3, 1, matrix[3][1], null, defaultTiming);
		table.put(3, 2, matrix[3][2], null, defaultTiming);
		table.put(4, 1, matrix[4][1], null, defaultTiming);
		table.put(4, 2, matrix[4][2], null, defaultTiming);
		table.highlightCell(3, 1, null, defaultTiming);
		table.highlightCell(3, 2, null, defaultTiming);
		table.highlightCell(4, 1, null, defaultTiming);
		table.highlightCell(4, 2, null, defaultTiming);
		lang.nextStep();

		table.unhighlightCell(3, 1, null, defaultTiming);
		table.unhighlightCell(3, 2, null, defaultTiming);
		table.unhighlightCell(4, 1, null, defaultTiming);
		table.unhighlightCell(4, 2, null, defaultTiming);
		exec("init_sign");
		lang.nextStep();

		exec("while");
		lang.nextStep();

		
		for (int i = 2; i < matrix[0].length-1; i++) {
			exec("calc_r");
			textR.setText(R + matrix[1][i + 1], null, defaultTiming);
			table.put(1, i + 1, matrix[1][i + 1], null, defaultTiming);
			table.highlightCell(1, i + 1, null, defaultTiming);
			lang.nextStep();

			exec("calc_q");
			table.unhighlightCell(1, i + 1, null, defaultTiming);
			textQ.setText(Q + matrix[2][i], null, defaultTiming);
			table.put(2, i, matrix[2][i], null, defaultTiming);
			table.highlightCell(2, i, null, defaultTiming);
			lang.nextStep();

			table.unhighlightCell(2, i, null, defaultTiming);
			textA.setText(A + matrix[1][i], null, defaultTiming);
			table.highlightCell(1, i, null, defaultTiming);
			exec("calc_a");
			lang.nextStep();

			table.unhighlightCell(1, i, null, defaultTiming);
			table.highlightCell(1, i + 1, null, defaultTiming);
			textB.setText(B + matrix[1][i + 1], null, defaultTiming);
			exec("calc_b");
			lang.nextStep();

			table.unhighlightCell(1, i + 1, null, defaultTiming);
			exec("calc_xx");
			lang.nextStep();

			exec("calc_yy");
			lang.nextStep();

			table.highlightCell(3, i + 1, null, defaultTiming);
			table.put(3, i + 1, matrix[3][i + 1], null, defaultTiming);
			exec("calc_xs1");
			lang.nextStep();

			table.unhighlightCell(3, i + 1, null, defaultTiming);
			table.highlightCell(4, i + 1, null, defaultTiming);
			table.put(4, i + 1, matrix[4][i + 1], null, defaultTiming);
			exec("calc_ys1");
			lang.nextStep();

			table.unhighlightCell(4, i + 1, null, defaultTiming);
			exec("calc_xs0");
			lang.nextStep();

			exec("calc_ys0");
			lang.nextStep();

			exec("calc_sign");
			lang.nextStep();
		}

		exec("end_while");
		lang.nextStep();

		textX.setText(X + x, null, defaultTiming);
		textX.show();
		exec("calc_x");
		lang.nextStep();

		textY.setText(Y + y, null, defaultTiming);
		textY.show();
		exec("calc_y");
		lang.nextStep();
		textGCD.setText(GCD + gcd, null, defaultTiming);
		textGCD.show();
		exec("calc_gcd");
		lang.nextStep();
		
		exec("end_function");
	}

	/*
	 * Felder fuer erweiterterEuklid
	 */
	private int gcd;
	private int x;
	private int y;

	/*
	 * Methoden fuer erweiterterEuklid
	 */

	/**
	 * <h3>Erweiterter Euklid</h3>
	 * <p>
	 * x*a+y*b = gcd <\p>
	 * 
	 * @param a
	 * @param b
	 * 	AQ
	 * '	Q!WHB-:	 * @param x
	 * @param y
	 */
	public void erweiterterEuklid(int a, int b) {
		rk.add(a + "");
		rk.add(b + "");
		qk.add("");
		int q, r, xx, yy, sign;
		int xs0, xs1, ys0, ys1;
		xs0 = 1;
		xk.add(xs0 + "");
		xs1 = 0;
		xk.add(xs1 + "");
		ys0 = 0;
		yk.add(ys0 + "");
		ys1 = 1;
		yk.add(ys1 + "");
		sign = 1;
		while (b != 0) {
			r = a % b;
			rk.add(r + "");
			q = a / b;
			qk.add(q + "");
			a = b;
			b = r;
			xx = xs1;
			yy = ys1;
			xs1 = q * xs1 + xs0;
			xk.add(xs1 + "");
			ys1 = q * ys1 + ys0;
			yk.add(ys1 + "");
			xs0 = xx;
			ys0 = yy;
			sign = -sign;
		}
		x = sign * xs0;
		y = -sign * ys0;
		gcd = a;
	}

	public String[][] toStringMatrix() {
		String[][] matrix = new String[5][rk.size() + 1];
		matrix[0][0] = "k";
		matrix[1][0] = "rk";
		matrix[2][0] = "qk";
		matrix[3][0] = "xk";
		matrix[4][0] = "yk";
		for (int i = 0; i < rk.size(); i++) {
			matrix[0][i + 1] = i + "";
			matrix[1][i + 1] = rk.get(i);
			if (i >= qk.size()) {
				matrix[2][i + 1] = "";
			} else {
				matrix[2][i + 1] = qk.get(i);
			}
			matrix[3][i + 1] = xk.get(i);
			matrix[4][i + 1] = yk.get(i);

		}
		return matrix;
	}

	public static void main(String[] args) {
		ErweiterterEuklid erweiterterEuklid = new ErweiterterEuklid();
		erweiterterEuklid.generateForMain();
		SimpleFileWriter.writeFile(new File("ErweiterterEuklid.asu"),
				erweiterterEuklid.lang.getAnimationCode());
	}

	@Override
	public String generate(AnimationPropertiesContainer arg0,
			Hashtable<String, Object> arg1) {
		init();
		generateText();
		defaultTiming = new TicksTiming(100);
		int a = (Integer) arg1.get("a");
		int b = (Integer) arg1.get("b");
		erweiterterEuklid(a, b);
		algorithmus();
		return lang.toString();
	}
	
	public void generateForMain(){
		init();
		generateText();
		defaultTiming = new TicksTiming(100);
		
		erweiterterEuklid(1000, 27);
		algorithmus();
	}

	@Override
	public String getAlgorithmName() {
		return HEADER_TITLE;
	}

	@Override
	public String getAnimationAuthor() {
		return AUTHOR;
	}

	@Override
	public String getCodeExample() {
		return pseudoCodeBuchmann;
	}

	@Override
	public Locale getContentLocale() {
		return Locale.GERMANY;
	}

	@Override
	public String getDescription() {
		return text;
	}

	@Override
	public String getFileExtension() {
		return ".asu";
	}

	@Override
	public GeneratorType getGeneratorType() {
		return new GeneratorType(GeneratorType.GENERATOR_TYPE_MORE);
	}

	@Override
	public String getName() {
		return HEADER_TITLE + " " + P_CODE_TITLE;
	}

	@Override
	public String getOutputLanguage() {
		return Generator.PSEUDO_CODE_OUTPUT;
	}

	@Override
	public String getAnnotatedSrc() {
		return pseudoCodeBuchmann;
	}
}